﻿#region License

// Another free, open solution by folks who get it.
// Copyright © 2011 Business in Unison, Inc.
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

#endregion

using System;
using System.ComponentModel;
using System.Linq;
using System.Reflection;

using AutoMapper;

using Castle.DynamicProxy;

namespace Patterns.Interception
{
	/// <summary>
	/// Defines extensions that use interception to enhance the target object's behavior.
	/// </summary>
	public static class InterceptionExtensions
	{
		/// <summary>
		/// Creates a clone of the current instance that automatically satisfies the
		/// requirements of the current <see cref="INotifyPropertyChanged"/> instance.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="instance">The instance.</param>
		/// <param name="filter">The filter.</param>
		/// <returns></returns>
		public static T CloneWithAutoNotify<T>(this T instance, Func<MethodInfo, bool> filter = null) where T : class, INotifyPropertyChanged
		{
			return instance.CloneAndIntercept(new LambdaInterceptor()
				.RunCondition(invocation => filter == null ? true : filter(invocation.Method))
				.After(invocation =>
				{
					if (!invocation.Method.Name.StartsWith("set_")) return;
					string propertyName = invocation.Method.Name.Substring(4);
					FieldInfo info = invocation.TargetType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic)
						.Where(f => f.FieldType.Equals(typeof (PropertyChangedEventHandler)))
						.FirstOrDefault();
					if (info == null) return;
					var handler = info.GetValue(invocation.InvocationTarget) as PropertyChangedEventHandler;
					if (handler != null) handler.Invoke(invocation.TargetType, new PropertyChangedEventArgs(propertyName));
				}));
		}

		/// <summary>
		/// Provides an open-ended pointcut to the CLR. see
		/// http://en.wikipedia.org/wiki/Pointcut for more info.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="instance">The instance.</param>
		/// <param name="interceptors">The interceptors.</param>
		/// <remarks>Remember that this method has the downside of only returning a proxy
		/// to a clone of the current instance.  Wrapping the current instance and
		/// returning the wrapper as a proxy needs to be investigated.</remarks>
		public static T CloneAndIntercept<T>(this T instance, params IInterceptor[] interceptors) where T : class
		{
			var target = new ProxyGenerator().CreateClassProxy<T>(interceptors);
			Mapper.DynamicMap(instance, target);
			return target;
		}
	}
}